﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the Microsoft Public License (Ms-PL).
// See accompanying file License.txt or copy at http://mews.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Mews.Mime
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.IO;

    using Mews.IO;

    /// <summary>The common base class of all entity bodies (see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC
    /// 2045</a> 2.4. &amp; 2.6.).</summary>
    /// <threadsafety static="true" instance="false"/>
    public abstract class Body : IVisitable<Body>
    {
        private readonly string subtype;
        private readonly FallbackReason fallbackReason;
        private Disposition disposition;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Gets the subtype of the body.</summary>
        /// <remarks>Is never <c>null</c> or empty. Parsed subtypes are always all-lowercase.</remarks>
        public string Subtype
        {
            get { return this.subtype; }
        }

        /// <summary>Gets or sets the disposition of the body.</summary>
        /// <remarks>The default value is <c>null</c>, which implementing classes set during construction and when the
        /// parser does not find a valid disposition. If the value of this property is <c>null</c>, clients can assume
        /// <see cref="DispositionKind.Attachment"/> or <see cref="DispositionKind.Inline"/>, see
        /// <a href="http://www.ietf.org/rfc/rfc2183.txt">RFC 2183</a>, 2.</remarks>
        public Disposition Disposition
        {
            get { return this.disposition; }
            set { this.disposition = value; }
        }

        /// <summary>Gets the fallback reason.</summary>
        /// <remarks>Is <c>null</c> when no fallback occurred.</remarks>
        public FallbackReason FallbackReason
        {
            get { return this.fallbackReason; }
        }

        /// <summary>See <see cref="IVisitable{T}.Accept"/>.</summary>
        public abstract void Accept(IVisitor<Body> visitor);

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Initializes a new instance of the <see cref="Body"/> class.</summary>
        internal Body(string subtype, FallbackReason fallbackReason)
        {
            this.subtype = subtype;
            this.fallbackReason = fallbackReason;
        }

        // TODO: This should probably be protected
        internal IEnumerable<HeaderField> GetRequiredHeaderFields(
            string mediaType,
            IEnumerable<KeyValuePair<string, string>> additionalParameters,
            string contentTransferEncoding)
        {
            var contentType =
                new ParameterizedHeaderField(HeaderFieldNames.ContentType, mediaType + "/" + this.Subtype);

            foreach (var param in additionalParameters)
            {
                AddParameter(contentType, param.Key, param.Value);
            }

            yield return contentType;

            if (contentTransferEncoding != ContentTransferEncodingNames.SevenBit)
            {
                yield return new ParameterizedHeaderField(
                    HeaderFieldNames.ContentTransferEncoding, contentTransferEncoding);
            }

            if (this.disposition != null)
            {
                var result = new ParameterizedHeaderField(
                    HeaderFieldNames.ContentDisposition,
                    this.disposition.Kind.ToString().ToLower(CultureInfo.InvariantCulture));
                AddParameter(result, ContentDispositionParameterNames.FileName, this.disposition.FileName);
                yield return result;
            }
        }

        internal static void AddParameter(ParameterizedHeaderField field, string name, string value)
        {
            if ((name != null) && (value != null))
            {
                field.Parameters.Add(name, value);
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Writes the content of this body into the stream.</summary>
        /// <param name="stream">The stream to write the body content to.</param>
        /// <param name="writeHeaderFieldsCallback">A delegate to send back to the caller the header fields that are
        /// minimally required to ensure conformance with RFCs. This delegate must be called exactly once
        /// <strong>before</strong> this method writes its first byte into <paramref name="stream"/>. Moreover, it must
        /// be called even if the passed collection of header fields is empty. If it is called with a <c>null</c>
        /// argument, then an <see cref="ArgumentNullException"/> will be thrown.</param>
        /// <remarks><strong>Rationale</strong>: The values and parameters of the fields that need to be incorporated
        /// into the header for a given body can often only be determined by doing at least a part of the conversions
        /// that are necessary when the body is written to the stream. The callback parameter allows this method to do
        /// the first part of the necessary body content conversions, store the result into a local variable, call
        /// <paramref name="writeHeaderFieldsCallback"/> and then convert the intermediate result into the final form
        /// and write that into the stream.</remarks>
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Fixing this one would require the declaration of a new delegate type.")]
        protected internal abstract void WriteTo(
            Stream stream, Action<IEnumerable<HeaderField>> writeHeaderFieldsCallback);
    }
}
